﻿using System;
using System.Text;
using System.Text.Json;
using System.IO;
//using Polly;
using Microsoft.Extensions.DependencyInjection;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using Chromely.Core.Logging;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Reader.Core
{
    /// <summary>
    /// 单例模式初始化
    /// </summary>
    public class Singleton
    {
        private HttpClient http;
        private static Singleton instance;
        private Singleton() { }
        public static Singleton Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new Singleton();
                }
                return instance;
            }
        }
        public HttpClient Client
        {
            get
            {
                if (http == null)
                {
                    var handler = new HttpClientHandler()
                    {
                        AllowAutoRedirect = true,
                        UseProxy = false,
                        AutomaticDecompression = DecompressionMethods.GZip,
                        MaxAutomaticRedirections = 5,
                        MaxConnectionsPerServer = 20, //20个最大连接数，针对同一服务器
                        ServerCertificateCustomValidationCallback = CheckValidationResult,
                        UseCookies = true,
                        CookieContainer = new CookieContainer()
                    };
                    ServicePointManager.ServerCertificateValidationCallback = CheckValidationResult;

                    var provider = new ServiceCollection()
                        //.AddResponseCompression(options =>
                        //{
                        //    options.Providers.Add<BrotliCompressionProvider>();
                        //    options.Providers.Add<GzipCompressionProvider>();
                        //})
                        //.Configure<BrotliCompressionProviderOptions>(options => options.Level = System.IO.Compression.CompressionLevel.Fastest)
                        .AddHttpClient("default", client =>
                        {
                            client.Timeout = TimeSpan.FromSeconds(30);
                            client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36");
                            client.DefaultRequestHeaders.Add("accept-encoding", "gzip, deflate");//, br");
                            client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9");
                            client.DefaultRequestHeaders.Add("Accept-Language", "zh,en-US;q=0.9,en;q=0.8,zh-CN;q=0.7,zh-HK;q=0.6,zh-TW;q=0.5");
                            client.DefaultRequestHeaders.Add("Connection", "Keep-Alive");
                        })
                        .ConfigurePrimaryHttpMessageHandler(_ =>
                        {
                            return handler;
                        })
                        //.AddTransientHttpErrorPolicy(build => build.WaitAndRetryAsync(new[]
                        //{
                        //    TimeSpan.FromSeconds(1),
                        //    TimeSpan.FromSeconds(5),
                        //    TimeSpan.FromSeconds(10)
                        //}))
                        .Services.BuildServiceProvider();
                    var factory = provider.GetService<IHttpClientFactory>();
                    http = factory.CreateClient("default");
                    http.Timeout = TimeSpan.FromSeconds(15);
                    //http = factory.CreateClient();
                    //http.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36 Edg/80.0.361.50");
                    //http.DefaultRequestHeaders.Add("accept-encoding", "gzip, deflate, br");
                    //http.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9");
                    //http.DefaultRequestHeaders.Add("Accept-Language", "zh,en-US;q=0.9,en;q=0.8,zh-CN;q=0.7,zh-HK;q=0.6,zh-TW;q=0.5");
                    //http.DefaultRequestHeaders.Add("Connection", "Keep-Alive");

                    Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); //解决找不到GB2312编码的问题 GBK还是不行
                }
                return http;
            }
        }
        private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
        {
            return true; //总是接受  
        }
    }
    public enum ResultType
    {
        String = 0,
        Stream = 1,
        Buffer = 2
    }
    public class HttpClientHelper
    {
        #region private
        private enum HttpContentType
        {
            JSON,
            FORM
        }
        private static string GetContentType(HttpContentType type)
        {
            switch (type)
            {
                case HttpContentType.JSON:
                    return "application/json";
                default:
                    return "application/x-www-form-urlencoded";
            }
        }

        #endregion

        #region get
        public static HttpResult GetRequestMessage(string url)
        {
            var result = new HttpResult(true);
            try
            {
                var response = Singleton.Instance.Client.GetAsync(url).Result;
                response.EnsureSuccessStatusCode();
                result.Request = response.RequestMessage;
                result.Url = response.RequestMessage.RequestUri;
            }
            catch (HttpRequestException e)
            {
                Logger.Instance.Log.Error(e);
                result.Success = false;
                result.Message = e.Message;
                result.exception = e;
            }
            return result;
        }
        public static HttpResult Get(string url, ResultType type = 0)
        {
            var result = new HttpResult(true);
            try
            {
                var response = Singleton.Instance.Client.GetAsync(url).Result;
                response.EnsureSuccessStatusCode();
                result.Status = response.StatusCode;
                result.Url = response.RequestMessage.RequestUri;
                switch (type)
                {
                    case ResultType.String:
                        result.Html = response.Content.ReadAsStringAsync().Result;
                        break;
                    case ResultType.Stream:
                        result.Stream = response.Content.ReadAsStreamAsync().Result;
                        break;
                    case ResultType.Buffer:
                        result.Buffer = response.Content.ReadAsByteArrayAsync().Result;
                        break;
                    default:
                        result.Success = false;
                        result.Message = "unrecognized data returned";
                        break;
                }
            }
            catch (HttpRequestException e)
            {
                Logger.Instance.Log.Error(e);
                result.Success = false;
                result.Message = e.Message;
                result.exception = e;
            }
            catch (Exception e)
            {
                Logger.Instance.Log.Error(e);
                result.Success = false;
                result.Message = e.Message;
                result.exception = e;
            }
            return result;
        }
        public static HttpResult Get(string url, IDictionary<string, string> param, ResultType type = 0)
        {
            string argv = "?";
            foreach (var item in param)
            {
                argv += $"{item.Key}={item.Value}&";
            }
            argv.TrimEnd('&');
            return Get(url + argv, type);
        }
        public static HttpResult Get(string url, IDictionary<string, string> param, IDictionary<string, string> headers, ResultType type = 0)
        {
            foreach (var header in headers)
            {
                Singleton.Instance.Client.DefaultRequestHeaders.Add(header.Key, header.Value);
            }
            return Get(url, param, type);
        }

        public static async Task<HttpResult> GetRequestMessageAsync(string url)
        {
            var result = new HttpResult(true);
            try
            {
                var response = await Singleton.Instance.Client.GetAsync(url);
                response.EnsureSuccessStatusCode();
                result.Request = response.RequestMessage;
                result.Url = response.RequestMessage.RequestUri;
            }
            catch (HttpRequestException e)
            {
                Logger.Instance.Log.Error(e);
                result.Success = false;
                result.Message = e.Message;
                result.exception = e;
            }
            catch (Exception e)
            {
                Logger.Instance.Log.Error(e);
                result.Success = false;
                result.Message = e.Message;
                result.exception = e;
            }
            return result;
        }
        public static async Task<HttpResult> GetAsync(string url, ResultType type = 0)
        {
            var result = new HttpResult(true);
            try
            {
                var response = await Singleton.Instance.Client.GetAsync(url);
                response.EnsureSuccessStatusCode();
                result.Status = response.StatusCode;
                result.Url = response.RequestMessage.RequestUri;
                switch (type)
                {
                    case ResultType.String:
                        result.Html = await response.Content.ReadAsStringAsync();
                        break;
                    case ResultType.Stream:
                        result.Stream = await response.Content.ReadAsStreamAsync();
                        break;
                    case ResultType.Buffer:
                        result.Buffer = await response.Content.ReadAsByteArrayAsync();
                        break;
                    default:
                        result.Success = false;
                        result.Message = "unrecognized data returned";
                        break;
                }
            }
            catch (HttpRequestException e)
            {
                Logger.Instance.Log.Error(e);
                result.Success = false;
                result.Message = e.Message;
                result.exception = e;
            }
            catch (Exception e)
            {
                Logger.Instance.Log.Error(e);
                result.Success = false;
                result.Message = e.Message;
                result.exception = e;
            }
            return result;
        }
        public static async Task<HttpResult> GetAsync(string url, IDictionary<string, string> param, ResultType type = 0)
        {
            string argv = "?";
            foreach (var item in param)
            {
                argv += $"{item.Key}={item.Value}&";
            }
            argv.TrimEnd('&');
            return await GetAsync(url + argv, type);
        }
        public static async Task<HttpResult> GetAsync(string url, IDictionary<string, string> param, IDictionary<string, string> headers, ResultType type = 0)
        {
            foreach (var header in headers)
            {
                Singleton.Instance.Client.DefaultRequestHeaders.Add(header.Key, header.Value);
            }
            return await GetAsync(url, param, type);
        }
        #endregion

        #region post
        public static HttpResult Post(string url, HttpContent content, ResultType type = 0)
        {
            var result = new HttpResult(true);
            try
            {
                var response = Singleton.Instance.Client.PostAsync(url, content).Result;
                response.EnsureSuccessStatusCode();
                result.Status = response.StatusCode;
                result.Url = response.RequestMessage.RequestUri;
                switch (type)
                {
                    case ResultType.String:
                        result.Html = response.Content.ReadAsStringAsync().Result;
                        break;
                    case ResultType.Stream:
                        result.Stream = response.Content.ReadAsStreamAsync().Result;
                        break;
                    case ResultType.Buffer:
                        result.Buffer = response.Content.ReadAsByteArrayAsync().Result;
                        break;
                    default:
                        result.Success = false;
                        result.Message = "unrecognized data returned";
                        break;
                }
            }
            catch (HttpRequestException e)
            {
                Logger.Instance.Log.Error(e);
                result.Success = false;
                result.Message = e.Message;
                result.exception = e;
            }
            catch (Exception e)
            {
                Logger.Instance.Log.Error(e);
                result.Success = false;
                result.Message = e.Message;
                result.exception = e;
            }
            return result;
        }
        public static HttpResult Post(string url, string json, IDictionary<string, string> headers = null, ResultType type = 0)
        {
            HttpContent content = new StringContent(json);
            content.Headers.ContentType = new MediaTypeHeaderValue(GetContentType(HttpContentType.JSON));
            if (headers != null)
            {
                foreach (var header in headers)
                {
                    content.Headers.Add(header.Key, header.Value);
                }
            }
            return Post(url, content, type);
        }
        public static HttpResult Post(string url, IDictionary<string, string> param, ResultType type = 0)
        {
            var json = JsonSerializer.Serialize(param);
            return Post(url, json, headers: null, type);
        }
        public static HttpResult Post(string url, IDictionary<string, string> param, IDictionary<string, string> headers, ResultType type = 0)
        {
            var json = JsonSerializer.Serialize(param);
            return Post(url, json, headers, type);
        }

        public static async Task<HttpResult> PostAsync(string url, HttpContent content, ResultType type = 0)
        {
            var result = new HttpResult(true);
            try
            {
                var response = await Singleton.Instance.Client.PostAsync(url, content);
                response.EnsureSuccessStatusCode();
                result.Status = response.StatusCode;
                result.Url = response.RequestMessage.RequestUri;
                switch (type)
                {
                    case ResultType.String:
                        result.Html = await response.Content.ReadAsStringAsync();
                        break;
                    case ResultType.Stream:
                        result.Stream = await response.Content.ReadAsStreamAsync();
                        break;
                    case ResultType.Buffer:
                        result.Buffer = await response.Content.ReadAsByteArrayAsync();
                        break;
                    default:
                        result.Success = false;
                        result.Message = "unrecognized data returned";
                        break;
                }
            }
            catch (HttpRequestException e)
            {
                Logger.Instance.Log.Error(e);
                result.Success = false;
                result.Message = e.Message;
                result.exception = e;
            }
            catch (Exception e)
            {
                Logger.Instance.Log.Error(e);
                result.Success = false;
                result.Message = e.Message;
                result.exception = e;
            }
            return result;
        }
        public static async Task<HttpResult> PostAsync(string url, string json, IDictionary<string, string> headers = null, ResultType type = 0)
        {
            HttpContent content = new StringContent(json);
            content.Headers.ContentType = new MediaTypeHeaderValue(GetContentType(HttpContentType.JSON));
            if (headers != null)
            {
                foreach (var header in headers)
                {
                    content.Headers.Add(header.Key, header.Value);
                }
            }
            return await PostAsync(url, content, type);
        }
        public static async Task<HttpResult> PostAsync(string url, IDictionary<string, string> param, ResultType type = 0)
        {
            var json = JsonSerializer.Serialize(param);
            return await PostAsync(url, json, headers: null, type);
        }
        public static async Task<HttpResult> PostAsync(string url, IDictionary<string, string> param, IDictionary<string, string> headers, ResultType type = 0)
        {
            var json = JsonSerializer.Serialize(param);
            return await PostAsync(url, json, headers, type);
        }
        #endregion
    }
    public class HttpResult : IAsyncDisposable, IDisposable
    {
        public bool Success { get; set; }
        public HttpRequestMessage Request { get; set; }
        public Uri Url { get; set; }
        public Exception exception { get; set; }
        public string Message { get; set; }
        public string Source { get; set; }
        public string StackTrace { get; set; }
        public HttpStatusCode Status { get; set; }
        public string Html { get; set; }
        public Stream Stream { get; set; }
        public byte[] Buffer { get; set; }
        public HttpResult() { }
        public HttpResult(bool success)
        {
            Success = success;
        }

        public ValueTask DisposeAsync()
        {
            return ((IAsyncDisposable)Stream).DisposeAsync();
        }

        #region IDisposable Support
        private bool disposedValue = false; // 要检测冗余调用

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: 释放托管状态(托管对象)。
                }

                // TODO: 释放未托管的资源(未托管的对象)并在以下内容中替代终结器。
                // TODO: 将大型字段设置为 null。

                disposedValue = true;
            }
        }

        // TODO: 仅当以上 Dispose(bool disposing) 拥有用于释放未托管资源的代码时才替代终结器。
        // ~HttpResult()
        // {
        //   // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
        //   Dispose(false);
        // }

        // 添加此代码以正确实现可处置模式。
        public void Dispose()
        {
            // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
            Dispose(true);
            // TODO: 如果在以上内容中替代了终结器，则取消注释以下行。
            GC.SuppressFinalize(this);
        }
        #endregion
    }
}
